#include <stdio.h>
#include <string.h>
#include <malloc.h>
#include "mediator.h"
#include "errno.h"
#include "plugin.h"
#include "simconn.h"
#include "simconndata.h"

sem_t simulator_free;
int bpnum = 0, bpqueuenum = 0, bpdelnum = 0, mdnum = 0;
struct break_point bplist[MAX_BREAKPOINT_NUM], bpqueue[MAX_BREAKPOINT_QUEUE];
struct break_point_del bpdelqueue[MAX_BREAKPOINT_DEL];
struct memory_dump mdlist[MAX_MEMORY_DUMP_QUEUE];

int mediator_listen() {
	int r;
	
	if ((r = simconn_connect()) < 0)
		return r;
	if ((r = plugin_listen()) < 0)
		return r;
	sem_init(&simulator_free, 0, 1);
	return 0;
}

void mediator_work() {
	while (1) {
		int i, j, r, index;
		
		while (simconn_wait_breakpoint(10, &index) < 0);
		sem_wait(&simulator_free);
		
		// commit breakpoint del queue
		for (i = 0; i < bpdelnum; i++) {
			for (j = 0; j < bpnum; j++)
				if (bplist[j].index == bpdelqueue[i].index)
					break;
			if (j < bpnum) {
				struct plugin_list *p = bplist[j].handler, **last = &bplist[j].handler;

				while (p != 0) {
					if (p->plugin_id == bpdelqueue[i].plugin_id)
						break;
					last = &p->next;
					p = p->next;
				}
				if (p != 0) {
					*last = p->next;
					free(p);

					r = 0;
					if (bplist[j].handler == 0) {
						r = simconn_del_breakpoint(bplist[j].index, bplist[j].type);
						bplist[j] = bplist[bpnum - 1];
						bpnum--;
					}
					plugin_notice_del_breakpoint(bpdelqueue[i].plugin_id, r);
				}
				else
					plugin_notice_del_breakpoint(bpdelqueue[i].plugin_id, -E_INVALID_PARAM);
			}
			else
				plugin_notice_del_breakpoint(bpdelqueue[i].plugin_id, -E_INVALID_PARAM);
		}
		bpdelnum = 0;
		// commit memory dump queue
		for (i = 0; i < mdnum; i++) {
			r = simconn_dump_memory(mdlist[i].addr, mdlist[i].data, mdlist[i].size);
			plugin_notice_dump_memory(mdlist[i].plugin_id, r);
		}
		mdnum = 0;
		// commit breakpoint queue
		for (i = 0; i < bpqueuenum; i++) {
			for (j = 0; j < bpnum; j++)
				if (bplist[j].type == bpqueue[i].type &&
					bplist[j].data_size == bpqueue[i].data_size &&
					!memcmp(bplist[j].buf, bpqueue[i].buf, bplist[j].data_size))
					break;
			if (j < bpnum) {
				bpqueue[i].handler->next = bplist[j].handler;
				bplist[j].handler = bpqueue[i].handler;
			}
			else {
				r = simconn_add_breakpoint(bpqueue[i].type, bpqueue[i].buf,bpqueue[i].data_size, &bpqueue[i].index);
				plugin_notice_add_breakpoint(bpqueue[i].handler->plugin_id, r);
				if (r == 0)
					bplist[bpnum++] = bpqueue[i];
			}
		}
		bpqueuenum = 0;
		
		// notice plugin for breakpoint
		if (index != -1) {
			for (i = 0; i < bpnum; i++)
				if (bplist[bpnum].index == index) break;
			if (i < bpnum) {
				int nreg;
				unsigned int regs[MAX_REGS];
				struct plugin_list *p = bplist[i].handler;
				
				simconn_reg(regs, &nreg);
				while (p != 0) {
					plugin_notice_breakpoint(p->plugin_id, regs, nreg);
					p = p->next;
				}
			} else
				fprintf(stderr, "chipmuck: received an unknow breakpoint, index=%d\n", index);
		}
		
		sem_post(&simulator_free);
		simconn_continue();
	}
}

void mediator_stop() {
	int r;
	
	if ((r = simconn_disconnect()) < 0)
		fprintf(stderr, "chipmuck: error on disconnecting simulator, errno=%d\n", -r);
	if ((r = plugin_stopall()) < 0)
		fprintf(stderr, "chipmuck: error on stopping plugins, errno=%d\n", -r);
	sem_destroy(&simulator_free);
}

int mediator_add_breakpoint(int type, const void *data, int len, int plugin_id) {
	if (bpqueuenum == MAX_BREAKPOINT_QUEUE)
		return -E_QUEUE_FULL;
	if (len > MAX_BREAKPOINT_DATA)
		return -E_INVALID_PARAM;
	sem_wait(&simulator_free);
	bpqueue[bpqueuenum].type = type;
	bpqueue[bpqueuenum].data_size = len;
	memcpy(bpqueue[bpqueuenum].buf, data, len);
	bpqueue[bpqueuenum].handler = (struct plugin_list *)malloc(sizeof(struct plugin_list));
	bpqueue[bpqueuenum].handler->plugin_id = plugin_id;
	bpqueue[bpqueuenum].handler->next = 0;
	bpqueuenum++;
	sem_post(&simulator_free);
	return 0;
}

int mediator_del_breakpoint(int index, int plugin_id) {
	if (bpdelnum == MAX_BREAKPOINT_DEL)
		return -E_QUEUE_FULL;
	sem_wait(&simulator_free);
	bpdelqueue[bpdelnum].index = index;
	bpdelqueue[bpdelnum].plugin_id = plugin_id;
	bpdelnum++;
	sem_post(&simulator_free);
}

int mediator_dump_memory(unsigned int addr, void *data, int size, int plugin_id) {
	if (mdnum == MAX_BREAKPOINT_QUEUE)
		return -E_QUEUE_FULL;
	sem_wait(&simulator_free);
	mdlist[mdnum].plugin_id = plugin_id;
	mdlist[mdnum].addr = addr;
	mdlist[mdnum].data = data;
	mdlist[mdnum].size = size;
	mdnum++;
	sem_post(&simulator_free);
	return 0;
}

